﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DSS_XNA.Engine.Error;
using DSS_XNA;
using DSS_XNA.Engine.Script.Statements;

namespace DSS_XNA.Engine.Script
{
    public class ScriptCommand
    {
        #region Properties

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private string _name = String.Empty;

        public If IfStatement
        {
            get { return _ifStatement; }
            set { _ifStatement = value; }
        }
        private If _ifStatement = null;

        public bool IsIfStatement
        {
            get
            {
                return this._ifStatement != null;
            }
        }

        public Dictionary<string, string> Parameters
        {
            get { return _parameters; }
            set { _parameters = value; }
        }
        private Dictionary<string, string> _parameters = new Dictionary<string, string>();

        public bool RunOnce
        {
            get { return _runOnce; }
            set { _runOnce = value; }
        }
        private bool _runOnce = false;

        public int CallCount
        {
            get { return _callCount; }
            set { _callCount = value; }
        }
        private int _callCount = 0;

        public delegate object ScriptBuildInFunctionActionDelegate(List<string> args, ScriptManager manager, Game1 engine);
        public delegate void ScriptCommandActionDelegate(Dictionary<string, string> args, ScriptManager manager, Game1 engine);
        public ScriptCommandActionDelegate ActionCommand;

        #endregion

        #region Public Methods

        public ScriptCommand()
        {
        }

        public bool Load(If ifStatement)
        {
            if (ifStatement == null)
                throw new DSSException("'ifStatement' is null.");

            this._ifStatement = ifStatement;
            return true;
        }

        public bool Load(string command)
        {
            if (command == null)
                throw new DSSException("'command' is null.");
            if (command.Length == 0)
                throw new DSSException("'command' is zero-length.");

            command = command.Trim();

            int wordCount = 0;
            string currentWord = String.Empty;
            command += ' '; // so the loop will pick up the last word - a kind of a hack really but it works...
            bool stringFound = false;
            foreach (char c in command)
            {
                // if we have found a string, read it in here
                if (c == '"')
                {
                    stringFound = !stringFound; // stop searching for a string if the end " is found - THIS WILL ONLY WORK IF stringFound IS INITED TO false
                }

                if (stringFound || c != ' ')
                {
                    currentWord += c;
                    continue;
                }

                // THIS WILL MEAN THAT YOU CANNOT HAVE ANY SUB STRINGS WITHIN A STRING - ALTHOUGH THAT WILL CRASH ANYWAYS THE SAME REASON IS DOES IN A NORMAL PROGRAMMING LANGUAGE
                currentWord = currentWord.Replace("\"", String.Empty);

                if (wordCount == 0)
                {
                    if (this.RunModifier(currentWord))
                    {
                        currentWord = String.Empty;
                        // world count does not get incremented because of this continue so the next interation of this loop will come back into this if statement
                        continue;
                    }

                    this.ActionCommand = ScriptCommands.AvaliableCommands[currentWord.ToLower()];
                    currentWord = String.Empty;
                }
                else
                {
                    string[] parts = currentWord.Split(':');

                    string name = parts[0];
                    string value = currentWord.Substring(name.Length + 1); // + 1 to remove the : after the variable name

                    this.Parameters.Add(name, value);
                    currentWord = String.Empty;
                }

                wordCount++;
            }

            return true;
        }

        public void Run(ScriptManager manager, Game1 engine)
        {
            if (manager == null)
                throw new DSSException("'manager' is null.");

            if (this.IsIfStatement)
            {
                bool result = this.EvaluateIfStatemenet(manager);

                if (result)
                    this.IfStatement.TrueClauseScript.Run(engine);
                else
                    this.IfStatement.FalseClauseScript.Run(engine);

                return;
            }

            // are we to only run this command once?
            if (this.RunOnce)
            {
                if (this.CallCount >= 1)
                    return;
            }

            this.ActionCommand(this.Parameters, manager, engine);

            this.CallCount++;
        }

        #endregion

        #region Private Methods

        private bool EvaluateIfStatemenet(ScriptManager manager)
        {
            if (!this.IsIfStatement)
                throw new DSSException("This command is not an IF statement.");

            object param1 = manager.GetParameterValue(this.IfStatement.LeftCondition);
            object param2 = manager.GetParameterValue(this.IfStatement.RightCondition);

            // AT THE MOMENT, ONLY INTEGARS ARE SUPPORTED, SIMPLY CHECK FOR THE TYPE AND COMPARE RESPECTFULLY
            // make sure the parameters are of the correct type
            double i1 = Convert.ToDouble(this.EvaluateIfStatemenetParameterType(param1));
            double i2 = Convert.ToDouble(this.EvaluateIfStatemenetParameterType(param2));

            bool result = true;
            switch (this.IfStatement.ConditionType)
            {
                case ConditionType.GreaterThan:
                    result = i1 > i2;
                    break;
                case ConditionType.LessThan:
                    result = i1 < i2;
                    break;
                case ConditionType.EqualToo:
                    result = i1 == i2;
                    break;
                default:
                    throw new DSSException(String.Format("'{0}' is an unknown Condition Type.", this.IfStatement.ConditionType));
            }

            return result;
        }

        private object EvaluateIfStatemenetParameterType(object p)
        {
            if (p is int)
                return (int)p;
            if (p is long)
                return (long)p;
            if (p is double)
                return (double)p;
            if (p is float)
                return (float)p;
            if (p is bool)
                return ((bool)p) == true ? 1 : 0; // convert the bool to an int because the only operators you can do on it are really == and !=

            // i'm not sure this is such a good idea, because if you want to check against a string or two strings that are integers, they will be turned into integets - but then maybe that isn't such a bad idea because == will still produce the same result -- maybe - if the strings havn't had .Trim() called...
            if (p is string)
            {
                int i;
                if (int.TryParse((string)p, out i))
                    return i;
                long l;
                if (long.TryParse((string)p, out l))
                    return l;
                float f;
                if (float.TryParse((string)p, out f))
                    return f;
                double d;
                if (double.TryParse((string)p, out d))
                    return d;
                bool b;
                if (bool.TryParse((string)p, out b))
                {
                    return b == true ? 1 : 0; // convert the bool to an int because the only operators you can do on it are really == and !=
                }
            }

            throw new DSSException(String.Format("'{0}' is an unknown type.", p));
        }

        private bool RunModifier(string command)
        {
            bool commandFound = false;

            if (command == "runonce")
            {
                this.RunOnce = true;
                commandFound = true;
            }

            return commandFound;
        }

        #endregion
    }
}
